#!/bin/env python
# -*- coding: utf-8 -*-


"""
File Name: filters.py
Author: morre <morre@vip.qq.com>
Create Time: 2022/12/31 16:58:47
Brief:
"""

from __future__ import absolute_import
import traceback
import hbase_client.pb.Filter_pb2 as pbFilter
import hbase_client.pb.Comparator_pb2 as pbComparator
from hbase_client.pb.HBase_pb2 import BytesBytesPair as pbBytesBytesPair

# You're brave to venture into this file.

FILTER_PATH = "org.apache.hadoop.hbase.filter."
COMPARATOR_PATH = "org.apache.hadoop.hbase.filter."

# Operators
MUST_PASS_ALL = 1
MUST_PASS_ONE = 2

# BitwiseOps
AND = 1
OR = 2
XOR = 3

# CompareTypes
LESS = 0
LESS_OR_EQUAL = 1
EQUAL = 2
NOT_EQUAL = 3
GREATER_OR_EQUAL = 4
GREATER = 5
NO_OP = 6



class FilterList:
    """
    A FilterList is also a Filter. But it's also a list of Filters with an
    operator. This allows you to build up complicated boolean expressions by
    chaining FilterLists.
    """


    def __init__(self, operator, *arg):
        self.filter_type = pbFilter.FilterList
        self.name = FILTER_PATH + "FilterList"
        self.operator = operator
        self.filters = []
        try:
            for incoming_filter in arg:
                self.filters.append(_to_filter(incoming_filter))
        except TypeError:
            # They passed a single filter and not a sequence of filters.
            self.filters.append(_to_filter(self.filters))

    def add_filters(self, *arg):
        for new_filter in arg:
            self.filters.append(_to_filter(new_filter))


class ColumnCountGetFilter:

    def __init__(self, limit):
        self.filter_type = pbFilter.ColumnCountGetFilter
        self.name = FILTER_PATH + "ColumnCountGetFilter"
        self.limit = limit


class ColumnPaginationFilter:

    def __init__(self, limit, offset, column_offset):
        self.filter_type = pbFilter.ColumnPaginationFilter
        self.name = FILTER_PATH + "ColumnPaginationFilter"
        self.limit = limit
        self.offset = offset
        self.column_offset = column_offset


class ColumnPrefixFilter:

    def __init__(self, prefix):
        self.filter_type = pbFilter.ColumnPrefixFilter
        self.name = FILTER_PATH + "ColumnPrefixFilter"
        self.prefix = prefix


class ColumnRangeFilter:
    """列区间过滤器"""

    def __init__(
        self, min_column, min_column_inclusive, max_column,
        max_column_inclusive):
        self.filter_type = pbFilter.ColumnRangeFilter
        self.name = FILTER_PATH + "ColumnRangeFilter"
        self.min_column = min_column
        self.min_column_inclusive = min_column_inclusive
        self.max_column = max_column
        self.max_column_inclusive = max_column_inclusive


class CompareFilter:

    def __init__(self, compare_op, comparator):
        self.filter_type = pbFilter.CompareFilter
        self.name = FILTER_PATH + "CompareFilter"
        self.compare_op = compare_op
        self.comparator = _to_comparator(comparator)


class DependentColumnFilter:
    """参考列过滤器，是一种允许用户指定一个参考列或引用列来过滤其他列的过滤器，过滤的原则是基于参考列的时间戳来进行筛选。"""

    def __init__(
        self, compare_filter, column_family, column_qualifier,
        drop_dependent_column):
        self.filter_type = pbFilter.DependentColumnFilter
        self.name = FILTER_PATH + "DependentColumnFilter"
        self.compare_filter = _to_filter(compare_filter)
        self.column_family = column_family
        self.column_qualifier = column_qualifier
        self.drop_dependent_column = drop_dependent_column


class FamilyFilter:

    def __init__(self, compare_filter):
        self.filter_type = pbFilter.FamilyFilter
        self.name = FILTER_PATH + "FamilyFilter"
        self.compare_filter = _to_filter(compare_filter)


class FilterWrapper:

    def __init__(self, new_filter):
        self.filter_type = pbFilter.FilterWrapper
        self.name = FILTER_PATH + "FilterWrapper"
        self.filter = _to_filter(new_filter)


class FirstKeyOnlyFilter:

    def __init__(self):
        self.filter_type = pbFilter.FirstKeyOnlyFilter
        self.name = FILTER_PATH + "FirstKeyOnlyFilter"


class FirstKeyValueMatchingQualifiersFilter:

    def __init__(self, qualifiers):
        self.filter_type = pbFilter.FirstKeyValueMatchingQualifiersFilter
        self.name = FILTER_PATH + "FirstKeyValueMatchingQualifiersFilter"
        self.qualifiers = qualifiers


class FuzzyRowFilter:
    """FuzzyRowFilter"""

    def __init__(self, fuzzy_keys_data):
        self.filter_type = pbFilter.FuzzyRowFilter
        self.name = FILTER_PATH + "FuzzyRowFilter"
        self.fuzzy_keys_data = []
        try:
            for fuzz in fuzzy_keys_data:
                self.fuzzy_keys_data.append(_to_bytes_bytes_pair(fuzz))
        except TypeError:
            # They passed a single element and not a sequence of elements.
            self.fuzzy_keys_data.append(_to_bytes_bytes_pair(fuzzy_keys_data))


class InclusiveStopFilter:

    def __init__(self, stop_row_key):
        self.filter_type = pbFilter.InclusiveStopFilter
        self.name = FILTER_PATH + "InclusiveStopFilter"
        self.stop_row_key = stop_row_key


class KeyOnlyFilter:

    def __init__(self, len_as_val):
        self.filter_type = pbFilter.KeyOnlyFilter
        self.name = FILTER_PATH + "KeyOnlyFilter"
        self.len_as_val = len_as_val


class MultipleColumnPrefixFilter:

    def __init__(self, sorted_prefixes):
        self.filter_type = pbFilter.MultipleColumnPrefixFilter
        self.name = FILTER_PATH + "MultipleColumnPrefixFilter"
        if isinstance(sorted_prefixes, list):
            self.sorted_prefixes = sorted_prefixes
        else:
            self.sorted_prefixes = [sorted_prefixes]


class PageFilter:

    def __init__(self, page_size):
        self.filter_type = pbFilter.PageFilter
        self.name = FILTER_PATH + "PageFilter"
        self.page_size = page_size


class PrefixFilter:

    def __init__(self, prefix):
        self.filter_type = pbFilter.PrefixFilter
        self.name = FILTER_PATH + "PrefixFilter"
        self.prefix = prefix


class QualifierFilter:

    def __init__(self, compare_filter):
        self.filter_type = pbFilter.QualifierFilter
        self.name = FILTER_PATH + "QualifierFilter"
        self.compare_filter = _to_pb_filter(compare_filter)


class RandomRowFilter:

    def __init__(self, chance):
        self.filter_type = pbFilter.RandomRowFilter
        self.name = FILTER_PATH + "RandomRowFilter"
        self.chance = chance


class RowFilter:

    def __init__(self, compare_filter):
        self.filter_type = pbFilter.RowFilter
        self.name = FILTER_PATH + "RowFilter"
        self.compare_filter = _to_filter(compare_filter)


class SkipColumnValueExcludeFilter:

    def __init__(self, single_column_value_filter):
        self.filter_type = pbFilter.SkipColumnValueExcludeFilter
        self.name = FILTER_PATH + "SkipColumnValueExcludeFilter"
        self.single_column_value_filter = _to_filter(
            single_column_value_filter)


class SkipColumnValueFilter:
    """SkipColumnValueFilter"""

    def __init__(
        self, compare_op, comparator, column_family, column_qualifier,
        filter_if_missing, latest_version_only):
        self.filter_type = pbFilter.SkipColumnValueFilter
        self.name = FILTER_PATH + "SkipColumnValueFilter"
        self.compare_op = compare_op
        self.comparator = _to_comparator(comparator)
        self.column_family = column_family
        self.column_qualifier = column_qualifier
        self.filter_if_missing = filter_if_missing
        self.latest_version_only = latest_version_only


class SkipFilter:
    """SkipFilter"""

    def __init__(self, orig_filter):
        self.filter_type = pbFilter.SkipFilter
        self.name = FILTER_PATH + "SkipFilter"
        self.filter = orig_filter


class TimestampsFilter:
    """TimestampsFilter"""

    def __init__(self, timestamps):
        self.filter_type = pbFilter.TimestampsFilter
        self.name = FILTER_PATH + "TimestampsFilter"
        if isinstance(timestamps, list):
            self.timestamps = timestamps
        else:
            self.timestamps = [timestamps]


class ValueFilter:

    def __init__(self, compare_filter):
        self.filter_type = pbFilter.ValueFilter
        self.name = FILTER_PATH + "ValueFilter"
        self.compare_filter = _to_filter(compare_filter)


class WhileMatchFilter:

    def __init__(self, orig_filter):
        self.filter_type = pbFilter.WhileMatchFilter
        self.name = FILTER_PATH + "WhileMatchFilter"
        self.filter = _to_filter(orig_filter)


class FilterAllFilter:

    def __init__(self):
        self.filter_type = pbFilter.FilterAllFilter
        self.name = FILTER_PATH + "FilterAllFilter"


class MultiRowRangeFilter:
    """MultiRowRangeFilter"""

    def __init__(self, row_range_list):
        self.filter_type = pbFilter.MultiRowRangeFilter
        self.name = FILTER_PATH + "MultiRowRangeFilter"
        self.row_range_list = []
        try:
            for row in row_range_list:
                self.row_range_list.append(_to_row_range(row))
        except TypeError:
            # They passed a single element and not a sequence of elements.
            self.row_range_list.append(_to_row_range(row_range_list))


# Instead of having to define a _to_filter method for every filter I
# instead opted to be hard core and define it once and support every
# filter.
#
# _to_filter will take any of the above classes, create the associated pb
# type, iterate over any special variables and set them accordingly,
# serialize the special pb filter type into a standard pb Filter object
# and return that.
def _to_filter(orig_filter):
    if orig_filter is None:
        return None
    ft = pbFilter.Filter()
    ft.name = orig_filter.name
    ft.serialized_filter = _to_pb_filter(orig_filter).SerializeToString()
    return ft


def _to_pb_filter(orig_filter):
    try:
        ft2 = orig_filter.filter_type()
        members = [attr for attr in dir(orig_filter) if not callable(
            attr) and not attr.startswith("__") and attr not in [
                "name", "filter_type", "add_filters"]]
        for member in members:
            try:
                val = getattr(orig_filter, member)
                if val is not None:
                    # skip none value that should be optional
                    setattr(ft2, member, val)
            except AttributeError:
                # It's a repeated element and we need to 'extend' it.
                el = getattr(ft2, member)
                try:
                    el.extend(getattr(orig_filter, member))
                except AttributeError:
                    # Just kidding. It's a composite field.
                    el.CopyFrom(getattr(orig_filter, member))
        return ft2
    except Exception as ex:
        raise ValueError(
            f"Malformed Filter provided, {ex} {traceback.format_exc()}") from ex


class ByteArrayComparable:

    def __init__(self, value):
        self.comparable_type = pbComparator.ByteArrayComparable
        self.value = value


# Just like _to_filter, but for comparables.
def _to_comparable(orig_cmp):
    try:
        new_cmp = orig_cmp.comparable_type()
        members = [attr for attr in dir(orig_cmp) if not callable(
            attr) and not attr.startswith("__") and attr not in [
                "name", "comparable_type"]]
        for member in members:
            val = getattr(orig_cmp, member)
            # skip none value that should be optional
            if val is not None:
                setattr(new_cmp, member, val)
        return new_cmp
    except Exception as ex:
        raise ValueError(
            f"Malformed Comparable provided {ex} "
            f"{traceback.format_exc()}") from ex


class BinaryComparator:

    def __init__(self, comparable):
        self.comparator_type = pbComparator.BinaryComparator
        self.name = COMPARATOR_PATH + "BinaryComparator"
        self.comparable = _to_comparable(comparable)


class LongComparator:

    def __init__(self, comparable):
        self.comparator_type = pbComparator.LongComparator
        self.name = COMPARATOR_PATH + "LongComparator"
        self.comparable = _to_comparable(comparable)


class BinaryPrefixComparator:

    def __init__(self, comparable):
        self.comparator_type = pbComparator.BinaryPrefixComparator
        self.name = COMPARATOR_PATH + "BinaryPrefixComparator"
        self.comparable = _to_comparable(comparable)


class BitComparator:

    def __init__(self, comparable, bitwise_op):
        self.comparator_type = pbComparator.BitComparator
        self.name = COMPARATOR_PATH + "BitComparator"
        self.comparable = _to_comparable(comparable)
        self.bitwise_op = bitwise_op


class NullComparator:

    def __init__(self):
        self.comparator_type = pbComparator.NullComparator
        self.name = COMPARATOR_PATH + "NullComparator"


class RegexStringComparator:

    def __init__(self, pattern, pattern_flags, charset, engine):
        self.comparator_type = pbComparator.RegexStringComparator
        self.name = COMPARATOR_PATH + "RegexStringComparator"
        self.pattern = pattern
        self.pattern_flags = pattern_flags
        self.charset = charset
        self.engine = engine


class StringComparator:

    def __init__(self, substr):
        self.comparator_type = pbComparator.BinaryPrefixComparator
        self.name = COMPARATOR_PATH + "BinaryPrefixComparator"
        self.substr = substr


# Just like _to_filter, but for comparators.
def _to_comparator(orig_cmp):
    try:
        new_cmp = pbComparator.Comparator()
        new_cmp.name = orig_cmp.name
        new_cmp2 = orig_cmp.comparator_type()
        members = [attr for attr in dir(orig_cmp) if not callable(
            attr) and not attr.startswith("__") and attr not in [
                "name", "comparator_type"]]
        for member in members:
            try:
                val = getattr(orig_cmp, member)
                if val is not None:
                    # skip none value that should be optional
                    setattr(new_cmp2, member, val)
            except AttributeError:
                # It's a composite element and we need to copy it in.
                el = getattr(new_cmp2, member)
                el.CopyFrom(getattr(orig_cmp, member))
        new_cmp.serialized_comparator = new_cmp2.SerializeToString()
        return new_cmp
    except Exception as ex:
        raise ValueError(
            f"Malformed Comparator provided {ex}"
            f" {traceback.format_exc()}") from ex


class BytesBytesPair:

    def __init__(self, first, second):
        self.first = first
        self.second = second


def _to_bytes_bytes_pair(bbp):
    try:
        new_bbp = pbBytesBytesPair()
        new_bbp.first = bbp.first
        new_bbp.second = bbp.second
        return new_bbp
    except Exception as exc:
        raise ValueError("Malformed BytesBytesPair provided") from exc


class RowRange:

    def __init__(
        self, start_row, start_row_inclusive, stop_row, stop_row_inclusive):
        self.filter_type = pbFilter.RowRange
        self.name = FILTER_PATH + "RowRange"
        self.start_row = start_row
        self.start_row_inclusive = start_row_inclusive
        self.stop_row = stop_row
        self.stop_row_inclusive = stop_row_inclusive


def _to_row_range(rr):
    try:
        new = pbFilter.RowRange()
        new.start_row = rr.start_row
        new.start_row_inclusive = rr.start_row_inclusive
        new.stop_row = rr.stop_row
        new.stop_row_inclusive = rr.stop_row_inclusive
        return new
    except Exception as exc:
        raise ValueError("Malformed RowRange provided") from exc
